/*!
 * 
 * Portamento  v1.1.1 - 2011-09-02
 * http://simianstudios.com/portamento
 *   
 * Copyright 2011 Kris Noble except where noted.
 * 
 * Dual-licensed under the GPLv3 and Apache 2.0 licenses: 
 * http://www.gnu.org/licenses/gpl-3.0.html
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 */
/**
 * 
 * Creates a sliding panel that respects the boundaries of
 * a given wrapper, and also has sensible behaviour if the
 * viewport is too small to display the whole panel.
 * 
 * Full documentation at http://simianstudios.com/portamento
 * 
 * ----
 * 
 * Uses the viewportOffset plugin by Ben Alman aka Cowboy:
 * http://benalman.com/projects/jquery-misc-plugins/#viewportoffset
 * 
 * Uses a portion of CFT by Juriy Zaytsev aka Kangax:
 * http://kangax.github.com/cft/#IS_POSITION_FIXED_SUPPORTED
 * 
 * Uses code by Matthew Eernisse:
 * http://www.fleegix.org/articles/2006-05-30-getting-the-scrollbar-width-in-pixels
 * 
 * Builds on work by Remy Sharp:
 * http://jqueryfordesigners.com/fixed-floating-elements/
 * 
 */
(function(jQuery){
  	
  jQuery.fn.portamento = function(options) {
		
    // we'll use the window and document objects a lot, so
    // saving them as variables now saves a lot of function calls
    var thisWindow = jQuery(window);
    var thisDocument = jQuery(document);
						
    /**
		 * NOTE by Kris - included here so as to avoid namespace clashes.
		 * 
		 * jQuery viewportOffset - v0.3 - 2/3/2010
		 * http://benalman.com/projects/jquery-misc-plugins/
		 * 
		 * Copyright (c) 2010 "Cowboy" Ben Alman
		 * Dual licensed under the MIT and GPL licenses.
		 * http://benalman.com/about/license/
		 */	
    jQuery.fn.viewportOffset = function() {
      var win = jQuery(window);
      var offset = jQuery(this).offset();
	  
      return {
        left: offset.left - win.scrollLeft(),
        top: offset.top - win.scrollTop()
      };
    };
		
    /**
		 * 
		 * A test to see if position:fixed is supported.
		 * Taken from CFT by Kangax - http://kangax.github.com/cft/#IS_POSITION_FIXED_SUPPORTED
		 * Included here so as to avoid namespace clashes.
		 * 
		 */
    function positionFixedSupported () {
      var container = document.body;
      if (document.createElement && container && container.appendChild && container.removeChild) {
        var el = document.createElement("div");
        if (!el.getBoundingClientRect) {
          return null;
        }
        el.innerHTML = "x";
        el.style.cssText = "position:fixed;top:100px;";
        container.appendChild(el);
        var originalHeight = container.style.height, originalScrollTop = container.scrollTop;
        container.style.height = "3000px";
        container.scrollTop = 500;
        var elementTop = el.getBoundingClientRect().top;
        container.style.height = originalHeight;
        var isSupported = elementTop === 100;
        container.removeChild(el);
        container.scrollTop = originalScrollTop;
        return isSupported;
      }
      return null;
    }
		
    /**
		 * 
		 * Get the scrollbar width by Matthew Eernisse.
		 * http://www.fleegix.org/articles/2006-05-30-getting-the-scrollbar-width-in-pixels
		 * Included here so as to avoid namespace clashes.
		 * 
		 */
    function getScrollerWidth() {
      var scr = null;
      var inn = null;
      var wNoScroll = 0;
      var wScroll = 0;
		
      // Outer scrolling div
      scr = document.createElement('div');
      scr.style.position = 'absolute';
      scr.style.top = '-1000px';
      scr.style.left = '-1000px';
      scr.style.width = '100px';
      scr.style.height = '50px';
      // Start with no scrollbar
      scr.style.overflow = 'hidden';
		
      // Inner content div
      inn = document.createElement('div');
      inn.style.width = '100%';
      inn.style.height = '200px';
		
      // Put the inner div in the scrolling div
      scr.appendChild(inn);
      // Append the scrolling div to the doc
      document.body.appendChild(scr);
		
      // Width of the inner div sans scrollbar
      wNoScroll = inn.offsetWidth;
      // Add the scrollbar
      scr.style.overflow = 'auto';
      // Width of the inner div width scrollbar
      wScroll = inn.offsetWidth;
		
      // Remove the scrolling div from the doc
      document.body.removeChild(document.body.lastChild);
		
      // Pixel width of the scroller
      return (wNoScroll - wScroll);
    }
		
    // ---------------------------------------------------------------------------------------------------
			    
    // get the definitive options
    var opts = jQuery.extend({}, jQuery.fn.portamento.defaults, options);
		
    // setup the vars accordingly
    var panel = this;
    var wrapper = opts.wrapper;
    var gap = opts.gap;
    var disableWorkaround = opts.disableWorkaround;
    var fullyCapableBrowser = positionFixedSupported();
		
    if(panel.length != 1) {
      // die gracefully if the user has tried to pass multiple elements
      // (multiple element support is on the TODO list!) or no elements...
      return this;
    }
		
    if(!fullyCapableBrowser && disableWorkaround) {
      // just stop here, as the dev doesn't want to use the workaround
      return this;
    }
		
    // wrap the floating panel in a div, then set a sensible min-height and width
    panel.wrap('<div id="portamento_container" />');
    var float_container = jQuery('#portamento_container');
    float_container.css({
      'min-height': panel.outerHeight(),
      'width': panel.outerWidth()
    });
		
    // calculate the upper scrolling boundary
    var panelOffset = panel.offset().top;
    var panelMargin = parseFloat(panel.css('marginTop').replace(/auto/, 0));
    var realPanelOffset = panelOffset - panelMargin;
    var topScrollBoundary = realPanelOffset - gap;
		
    // a couple of numbers to account for margins and padding on the relevant elements
    var wrapperPaddingFix = parseFloat(wrapper.css('paddingTop').replace(/auto/, 0));
    var containerMarginFix = parseFloat(float_container.css('marginTop').replace(/auto/, 0));
		
    // do some work to fix IE misreporting the document width
    var ieFix = 0;
		
    var isMSIE = /*@cc_on!@*/0;
		
    if (isMSIE) {
      ieFix = getScrollerWidth() + 4;
    }
						
    // ---------------------------------------------------------------------------------------------------
		
    thisWindow.bind("scroll.portamento", function () {
			
      if(thisWindow.height() > panel.outerHeight() && thisWindow.width() >= (thisDocument.width() - ieFix)) { // don't scroll if the window isn't big enough
				
        var y = thisDocument.scrollTop(); // current scroll position of the document
												
        if (y >= (topScrollBoundary)) { // if we're at or past the upper scrolling boundary
          if((panel.innerHeight() - wrapper.viewportOffset().top) - wrapperPaddingFix + gap >= wrapper.height()) { // if we're at or past the bottom scrolling boundary
            if(panel.hasClass('fixed') || thisWindow.height() >= panel.outerHeight()) { // check that there's work to do
              panel.removeClass('fixed');
              panel.css('top', (wrapper.height() - panel.innerHeight()) + 'px');
            }
          } else { // if we're somewhere in the middle
            panel.addClass('fixed');
						
            if(fullyCapableBrowser) { // supports position:fixed
              panel.css('top', gap + 'px'); // to keep the gap
            } else {
              panel.clearQueue();
              panel.css('position', 'absolute').animate({
                top: (0 - float_container.viewportOffset().top + gap)
                });
            }
          }
        } else {
          // if we're above the top scroll boundary
          panel.removeClass('fixed');
          panel.css('top', '0'); // remove any added gap
        }
      } else {
        panel.removeClass('fixed');
      }
    });
		
    // ---------------------------------------------------------------------------------------------------
		
    thisWindow.bind("resize.portamento", function () {
      // stop users getting undesirable behaviour if they resize the window too small
      if(thisWindow.height() <= panel.outerHeight() || thisWindow.width() < thisDocument.width()) {
        if(panel.hasClass('fixed')) {
          panel.removeClass('fixed');
          panel.css('top', '0');
        }
      } else {
        thisWindow.trigger('scroll.portamento'); // trigger the scroll event to place the panel correctly
      }
    });
		
    // ---------------------------------------------------------------------------------------------------
		
    thisWindow.bind("orientationchange.portamento", function () {
      // if device orientation changes, trigger the resize event
      thisWindow.trigger('resize.portamento');
    });
		
    // ---------------------------------------------------------------------------------------------------
		
    // trigger the scroll event immediately so that the panel is positioned correctly if the page loads anywhere other than the top.
    thisWindow.trigger('scroll.portamento');
		
    // return this to maintain chainability
    return this;
  };
	
  // set some sensible defaults
  jQuery.fn.portamento.defaults = {
    'wrapper' : jQuery('body'), // the element that will act as the sliding panel's boundaries
    'gap' : 0, // the gap (in pixels) left between the top of the viewport and the top of the panel
    'disableWorkaround': false // option to disable the workaround for not-quite capable browsers
  };
	
})(jQuery);